/**
 * 
 */
package net.gdface.sdk;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
/**   
 * @Title: CodeInfo.java 
 * @Package net.gdface.sdk 
 * @Description: 人脸信息存储类 
 * @author guyadong   
 * @date 2014-9-19 下午3:37:24 
 * @version V1.0   
 */
import java.io.Serializable;
import java.util.Arrays;

/**
 * 人脸特征码信息描述对象<br>
 * 包括人脸位置,眼睛、嘴巴,、鼻子位置,人脸角度，特征码数据
 * @author guyadong
 *
 */
public class CodeInfo implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	protected static final FInt2 ZERO_OFFSET = new FInt2(0,0);
	/**
	 * 特征码数组
	 */
	private byte[] code;
	/**
	 * 人脸位置信息（相对于原始图像）
	 */
	private FRect pos;
	/**
	 * 眼睛位置信息（相对于原始图像）
	 */
	private EyeInfo ei;

	/**
	 * 嘴巴位置
	 */
	private FInt2 mouth;
	/**
	 * 鼻子位置
	 */
	private FInt2 nose;
	/**
	 * 人脸角度
	 */
	private FAngle angle;	
	/**
	 * 面部数据
	 */
	private byte[] facialData;
	/**
	 * 人脸位置旋转角度(0,90,270)
	 */
	private int rotate;
	/**
	 * 人脸信息坐标偏移量<br>
	 * 此字段属于状态描述字段，不属于人脸信息，
	 * 此字段不为{@code null}时用于描述当前人脸信息对象中所有的坐标数据的原点相对实际原点的偏移量
	 */
	private FInt2 offset;
	public CodeInfo() {
		this(null,null,null,null,null,null, null);
	}

	public CodeInfo(byte[] code, FRect pos, EyeInfo ei, FInt2 mouth, FInt2 nose, FAngle angle, byte[] facialData) {
		this.code = code;
		this.pos = pos;
		this.ei = ei;
		this.mouth = mouth;
		this.nose = nose;
		this.angle = angle;
		this.facialData=facialData;
		this.rotate = 0;
	}

	public byte[] getCode() {
		return code;
	}

	/**
	 * @return the ei
	 */
	public EyeInfo getEi() {
		return ei;
	}

	public FRect getPos() {
		return pos;
	}

	public void setCode(byte[] code) {
		this.code = code;
	}

	/**
	 * @param ei the ei to set
	 */
	public void setEi(EyeInfo ei) {
		this.ei = ei;
	}

	public void setPos(FRect pos) {
		this.pos = pos;
	}

	/**
	 * @return mouth
	 */
	public FInt2 getMouth() {
		return mouth;
	}

	/**
	 * @param mouth 要设置的 mouth
	 */
	public void setMouth(FInt2 mouth) {
		this.mouth = mouth;
	}

	/**
	 * @return nose
	 */
	public FInt2 getNose() {
		return nose;
	}

	/**
	 * @param nose 要设置的 nose
	 */
	public void setNose(FInt2 nose) {
		this.nose = nose;
	}

	/**
	 * @return angle
	 */
	public FAngle getAngle() {
		return angle;
	}

	/**
	 * @param angle 要设置的 angle
	 */
	public void setAngle(FAngle angle) {
		this.angle = angle;
	}

	/**
	 * @return facialData
	 */
	public byte[] getFacialData() {
		return facialData;
	}

	/**
	 * @param facialData 要设置的 facialData
	 */
	public void setFacialData(byte[] facialData) {
		this.facialData = facialData;
	}
	
	/**
	 * 人脸位置旋转角度(0,90,270)
	 * @return 旋转角度(0,90,270)
	 */
	public int getRotate() {
		return rotate;
	}

	public void setRotate(int rotate) {
		this.rotate = rotate;
	}

	/**
	 * 人脸信息坐标偏移量<br>
	 * 此字段属于状态描述字段，不是属于人脸信息部分，
	 * 此字段不为{@code null}时用于描述当前人脸信息对象中所有的坐标数据的原点相对实际原点的偏移量
	 * @return 偏移量对象，为{@code null}无偏移或偏移为0
	 */
	public FInt2 getOffset() {
		return offset;
	}

	public void setOffset(FInt2 offset) {
		this.offset = offset;
	}
	public CodeInfo withOffset(int x,int y) {
		setOffset(new FInt2(x, y));
		return this;
	}
	/**
	 * 将当前人脸位置对象中的坐标数据根据偏移坐标({@link #offset})重新定位<br>
	 * 所有水平坐标 - offset.x<br>
	 * 所有垂直坐标 - offset.y<br>
	 * 返回重新定位的当前对象
	 * @return 返回重新定位的当前对象
	 */
	public final CodeInfo relocate(){
		if(null != offset && !ZERO_OFFSET.equals(offset) ){
			int x = offset.getX();
			int y = offset.getY();
			if(null != pos){
				pos.setLeft(pos.getLeft() - x);
				pos.setTop(pos.getTop() - y);
			}
			if(null != ei){
				ei.setLeftAndRight(ei.getLeftx() - x, ei.getLefty() - y, ei.getRightx() - x, ei.getRighty() - y);
			}
			if(null != mouth){
				mouth.setX(mouth.getX() - x);
				mouth.setY(mouth.getY() - y);
			}
			if(null != nose){
				nose.setX(nose.getX() - x);
				nose.setY(nose.getY() - y);
			}
			relocateFacialData();
			// 完成重定位后将 offset 置为null
			offset = null;
		}
		return this;
	}
	/**
	 * 对面部数据重新定位<br>
	 * 默认实现对面部数据({@link #facialData})不做任何修改，子类可重写此方法
	 */
	protected void relocateFacialData(){
		// DO NOTHING
	}
	@Override
	public String toString() {
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		toStream(new PrintStream(bo));
		return bo.toString();
	}
	/**
	 * 以文本形式向{@link PrintStream}输出对象内容 
	 * @param stream
	 */
	public void toStream(PrintStream stream) {
		stream.printf("[pos=%s,ei=%s,mouth=%s,nose=%s,angle=%s,offset=%s,code.length=%s,facialData.length=%s]"
				,this.pos,this.ei,this.mouth,this.nose,this.angle,this.offset
				,null==code?"null":Integer.toString(code.length)
				,null==facialData?"null":Integer.toString(facialData.length));
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((angle == null) ? 0 : angle.hashCode());
		result = prime * result + Arrays.hashCode(code);
		result = prime * result + ((ei == null) ? 0 : ei.hashCode());
		result = prime * result + Arrays.hashCode(facialData);
		result = prime * result + ((mouth == null) ? 0 : mouth.hashCode());
		result = prime * result + ((nose == null) ? 0 : nose.hashCode());
		result = prime * result + ((pos == null) ? 0 : pos.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof CodeInfo)) {
			return false;
		}
		CodeInfo other = (CodeInfo) obj;
		if (angle == null) {
			if (other.angle != null) {
				return false;
			}
		} else if (!angle.equals(other.angle)) {
			return false;
		}
		if (!Arrays.equals(code, other.code)) {
			return false;
		}
		if (ei == null) {
			if (other.ei != null) {
				return false;
			}
		} else if (!ei.equals(other.ei)) {
			return false;
		}
		if (!Arrays.equals(facialData, other.facialData)) {
			return false;
		}
		if (mouth == null) {
			if (other.mouth != null) {
				return false;
			}
		} else if (!mouth.equals(other.mouth)) {
			return false;
		}
		if (nose == null) {
			if (other.nose != null) {
				return false;
			}
		} else if (!nose.equals(other.nose)) {
			return false;
		}
		if (pos == null) {
			if (other.pos != null) {
				return false;
			}
		} else if (!pos.equals(other.pos)) {
			return false;
		}
		return true;
	}
}
